package com.winit.core.host.shell;

import com.alibaba.fastjson.JSONObject;
import com.winit.common.util.ConfigUtil;
import com.winit.common.util.Constants;
import com.winit.common.util.SeqGenerate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * Created by wangjian on 16/6/22.
 */
public class ShellerContainer {

    private static final Logger logger = LoggerFactory.getLogger(ShellerContainer.class);

    //默认保存日志文件目录
    private String defaultDir = ConfigUtil.getValue(ConfigUtil.ROOT_PATH)+"Beetle/logs/shell/";

    static ExecutorService shellerThreadPool = Executors.newFixedThreadPool(800);

    static Map<String,Sheller> shellerIdMap = new HashMap<>();

    static Map<String,List<Sheller>> shellerCmdMap = new HashMap<>();

    static Map<String,List<JSONObject>> shellerGroupMap = new HashMap<>();

    static Map<String,GroupListener> groupListeners = new HashMap<>();

    static Map<String,Timer> timerMap = new HashMap<>();

    public enum ShellerStatus{
        SHELLER_RUN("run"),
        SHELLER_STOP("stop"),
        SHELLER_SUCCESS("success"),
        SHELLER_ERROR("error"),
        SHELLER_TIME_OUT("timeOut");

        private String desc;

        ShellerStatus(String desc){
            this.desc = desc;
        }

        public String getDesc() {
            return desc;
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }
    }

    /**
     * 创建sheller
     * @param cmd   必填,非空   执行命令,例:bash /Users/wangjian/Desktop/test/test.sh
     * @param params    必填,可空   命令参数,例:1 2 3 4
     * @param shellId   必填,非空   sheller唯一id
     * @param logFilePath   必填,可空   日志文件路径.默认在Beetle/log/shell/log-{shellId}.log
     * @param runTime   可有可无    线程执行时长,单位:秒
     * @param ip    必填,可空  远程执行命令时,远程终端ip
     * @param sshPort   必填,可空 远程终端ssh端口
     * @param isSingle  可有可无    cmd是否单线程执行
     * @param listener  必填
     */
    public void createSheller(String cmd,String params,String shellId,String logFilePath,Integer runTime,String ip,
                              String sshPort,String sshUser,String sshPass,boolean isSingle,Sheller.Listener listener){
        logger.info("创建sheller,cmd="+cmd+",params="+params+",shellId="+shellId+",runTime="+runTime+",logFilePath="+logFilePath+",isSingle="+isSingle);

        if(isSingle && this.shellerCmdMap.containsKey(cmd)){
            logger.info("创建sheller,单例模式,cmd相同sheller已存在,isSingle="+isSingle+",cmd="+cmd);
            return;
        }

        this.createShellerP(cmd,params,shellId,logFilePath,runTime,ip,sshPort,sshUser,sshPass,listener);
    }

    public void createSheller(String cmd,String params,String shellId,String logFilePath,String ip,
                              String sshPort,String sshUser,String sshPass,boolean isSingle,Sheller.Listener listener){
        logger.info("创建sheller,cmd="+cmd+",params="+params+",shellId="+shellId+",logFilePath="+logFilePath+",isSingle="+isSingle);

        if(isSingle && this.shellerCmdMap.containsKey(cmd)){
            logger.info("创建sheller,单例模式,cmd相同sheller已存在,isSingle="+isSingle+",cmd="+cmd);
            return;
        }

        this.createShellerP(cmd,params,shellId,logFilePath,0,ip,sshPort,sshUser,sshPass,listener);
    }

    public void createSheller(String cmd,String params,String shellId,String logFilePath,Integer runTime,String ip,
                              String sshPort,String sshUser,String sshPass,Sheller.Listener listener){
        logger.info("创建sheller,cmd="+cmd+",params="+params+",shellId="+shellId+",runTime="+runTime+",logFilePath="+logFilePath);

        this.createShellerP(cmd,params,shellId,logFilePath,runTime,ip,sshPort,sshUser,sshPass,listener);
    }

    public void createSheller(String cmd,String params,String shellId,String logFilePath,String ip,
                              String sshPort,String sshUser,String sshPass,Sheller.Listener listener){
        logger.info("创建sheller,cmd="+cmd+",params="+params+",shellId="+shellId+",logFilePath="+logFilePath);

        this.createShellerP(cmd,params,shellId,logFilePath,0,ip,sshPort,sshUser,sshPass,listener);
    }

    public void createShellerWithGroup(String groupId,List<Sheller.ShellerObject> shells, String logFilePath, String ip,
                                       String sshPort, String sshUser, String sshPass, GroupListener listener){
        this.groupListeners.put(groupId,listener);
        for (Sheller.ShellerObject shell : shells){
            String cmd = shell.getCmd();
            String params = shell.getParams();
            String shellId = shell.getShellId();
            Integer runTime = shell.getRunTime();
            boolean isSingle = shell.isSingle();
            logger.info("创建sheller,cmd="+cmd+",params="+params+",shellId="+shellId+",runTime="+runTime+",logFilePath="+logFilePath+",isSingle="+isSingle);
            if(isSingle && this.shellerCmdMap.containsKey(cmd)){
                logger.info("创建sheller,单例模式,cmd相同sheller已存在,isSingle="+isSingle+",cmd="+cmd);
                return;
            }

            this.createShellerP(groupId,cmd,params,shellId,logFilePath,runTime,ip,sshPort,sshUser,sshPass,new Sheller.Listener() {
                @Override
                public void postPid(String shellId, String pid) {}
                @Override
                public void onClose(String shellId,String desc,String status,String groupId) {
                    Constants.SHELLER_CONTAINER.cleanSheller(shellId);
                    Constants.SHELLER_CONTAINER.updateGroupStatus(shellId,groupId);
                    if(!Constants.SHELLER_CONTAINER.shellerGroupMap.containsKey(groupId)){
                        return;
                    }
                    List<JSONObject> list = Constants.SHELLER_CONTAINER.shellerGroupMap.get(groupId);
                    if(list.size() == 0){
                        Constants.SHELLER_CONTAINER.groupClose(groupId);
                    }
                }
            },shell.getListener());
        }
    }

    private void createShellerP(String groupId,String cmd,String params,String shellId,String logFilePath,Integer runTime,String ip,
                                String sshPort,String sshUser,String sshPass,Sheller.Listener groupListener,Sheller.Listener shellListener){
        if(StringUtils.isEmpty(cmd) || StringUtils.isEmpty(shellId)){
            logger.info("创建sheller,参数存在空值");
            return;
        }
        this.stopSheller(shellId);

        logFilePath = StringUtils.isEmpty(logFilePath)?this.getDefaultLogFile(shellId):logFilePath;
        Sheller sheller = new Sheller(groupId,cmd,params,shellId,logFilePath,runTime,ip,sshPort,sshUser,sshPass,groupListener);
        sheller.addListener(shellListener);
        this.operateShell("add",shellId,cmd,sheller);

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("shellId",shellId);
        jsonObject.put("desc","");
        jsonObject.put("status","");
        jsonObject.put("close","1");

        this.operateShellGroup("add",groupId,jsonObject);
        this.shellerThreadPool.execute(sheller);
        if(this.timerMap.containsKey(shellId)){
            this.timerMap.remove(shellId);
        }
        if(runTime > 0){
            Timer timer = new Timer();
            timer.schedule(new ShellTimer(shellId),runTime*1000);
            this.timerMap.put(shellId,timer);
        }
    }


    private void createShellerP(String cmd,String params,String shellId,String logFilePath,Integer runTime,String ip,
                                String sshPort,String sshUser,String sshPass,Sheller.Listener listener){
        if(StringUtils.isEmpty(cmd) || StringUtils.isEmpty(shellId)){
            logger.info("创建sheller,参数存在空值");
            return;
        }
        this.stopSheller(shellId);

        logFilePath = StringUtils.isEmpty(logFilePath)?this.getDefaultLogFile(shellId):logFilePath;
        Sheller sheller = new Sheller("",cmd,params,shellId,logFilePath,runTime,ip,sshPort,sshUser,sshPass,listener);
        this.operateShell("add",shellId,cmd,sheller);
        this.shellerThreadPool.execute(sheller);
        if(this.timerMap.containsKey(shellId)){
            this.timerMap.remove(shellId);
        }
        if(runTime > 0){
            Timer timer = new Timer();
            timer.schedule(new ShellTimer(shellId),runTime*1000);
            this.timerMap.put(shellId,timer);
        }
    }

    public class ShellTimer extends TimerTask{

        private String shellId;

        public ShellTimer(String shellId){
            this.shellId = shellId;
        }

        @Override
        public void run() {
            Constants.SHELLER_CONTAINER.timeOutToStopSheller(shellId);
        }
    }


    public String getDefaultLogFile(String shellId){
        StringBuffer path = new StringBuffer();
        path.append(this.defaultDir).append("log-").append(shellId).append(".log");
        return path.toString();
    }

    public void timeOutToStopSheller(String shellId){
        logger.info("超时关闭sheller.shellId="+shellId);
        if(!this.shellerIdMap.containsKey(shellId)){
            logger.info("超时关闭sheller,sheller不存在.shellId="+shellId);
            return;
        }
        Sheller sheller = this.shellerIdMap.get(shellId);
        sheller.timeOutToStopSheller();
        String cmd = sheller.getCmd();
        this.operateShell("del",shellId,cmd,sheller);
    }

    public void stopSheller(String shellId){
        logger.info("关闭sheller.shellId="+shellId);
        if(!this.shellerIdMap.containsKey(shellId)){
            logger.info("关闭sheller,sheller不存在.shellId="+shellId);
            return;
        }
        Sheller sheller = this.shellerIdMap.get(shellId);
        sheller.stopSheller();
        String cmd = sheller.getCmd();
        this.operateShell("del",shellId,cmd,sheller);
    }

    public void cleanSheller(String shellId){
        if(this.shellerIdMap.containsKey(shellId)){
            Sheller sheller = this.shellerIdMap.get(shellId);
            String cmd = sheller.getCmd();
            this.operateShell("del",shellId,cmd,sheller);
        }
        if(this.timerMap.containsKey(shellId)){
            Timer timer = this.timerMap.get(shellId);
            timer.cancel();
            this.timerMap.remove(shellId);
        }
    }

    public String createShellId(){
        return SeqGenerate.getShellerId();
    }

    public String getShellerStatus(String shellId){
        logger.info("获取sheller状态.shellId="+shellId);
        if(this.shellerIdMap.containsKey(shellId)){
            return ShellerStatus.SHELLER_RUN.getDesc();
        }else {
            return ShellerStatus.SHELLER_STOP.getDesc();
        }
    }

    public Integer getShellerRunTime(String shellId){
        logger.info("获取sheller运行时长.shellId="+shellId);
        if(this.shellerIdMap.containsKey(shellId)){
            Sheller sheller = this.shellerIdMap.get(shellId);
            return sheller.getShellerRunTime();
        }else {
            logger.info("获取sheller运行时长,sheller不存在.shellId="+shellId);
            return 0;
        }
    }

    public Integer getShellerLeftTime(String shellId){
        logger.info("获取sheller剩余时长.shellId="+shellId);
        if(this.shellerIdMap.containsKey(shellId)){
            Sheller sheller = this.shellerIdMap.get(shellId);
            return sheller.getShellerLeftTime();
        }else {
            logger.info("获取sheller剩余时长,sheller不存在.shellId="+shellId);
            return 0;
        }
    }

    public String getLogFilePath(String shellId){
        logger.info("获取sheller日志路径.shellId="+shellId);
        if(this.shellerIdMap.containsKey(shellId)){
            Sheller sheller = this.shellerIdMap.get(shellId);
            return sheller.getLogFilePath();
        }else {
            logger.info("获取sheller日志路径,sheller不存在.shellId="+shellId);
            return "";
        }
    }

    public synchronized void updateGroupStatus(String shellId,String groupId){
        if(!this.shellerGroupMap.containsKey(groupId)){
            logger.info("groupId不存在,groupId="+groupId);
            return;
        }

        List<JSONObject> list = this.shellerGroupMap.get(groupId);
        if(list.size() == 0){
            return;
        }
        for(int i=0;i<list.size();i++){
            JSONObject jsonObject = list.get(i);
            if(shellId.equals(jsonObject.get("shellId"))){
                list.remove(i);
                this.shellerGroupMap.put(groupId,list);
                break;
            }
        }
    }

    public interface GroupListener {
        void onClose(String groupId);
    }

    public void groupClose(String groupId){
        if(!this.groupListeners.containsKey(groupId)){
            return;
        }
        this.groupListeners.get(groupId).onClose(groupId);
        this.groupListeners.remove(groupId);
        this.operateShellGroup("del",groupId,new JSONObject());
    }

    private synchronized void operateShell(String type,String shellId,String cmd,Sheller sheller){
        if("add".equals(type)){//新增sheller
            this.shellerIdMap.put(shellId,sheller);
            if(this.shellerCmdMap.containsKey(cmd)){
                List<Sheller> list = this.shellerCmdMap.get(cmd);
                list.add(sheller);
                this.shellerCmdMap.put(cmd,list);
            }else{
                List<Sheller> list = new ArrayList<>();
                list.add(sheller);
                this.shellerCmdMap.put(cmd,list);
            }
        }else if("del".equals(type)){//删除sheller
            if(this.shellerCmdMap.containsKey(cmd)){
                List<Sheller> list = this.shellerCmdMap.get(cmd);
                if(list.size() > 0){
                    list.remove(sheller);
                }
                if(list.size() == 0){
                    this.shellerCmdMap.remove(cmd);
                }else {
                    this.shellerCmdMap.put(cmd,list);
                }
            }
            if(this.shellerIdMap.containsKey(shellId)){
                this.shellerIdMap.remove(shellId);
            }
        }
    }

    private synchronized void operateShellGroup(String type,String groupId,JSONObject jsonObject){
        if("add".equals(type)){//添加组
            if(this.shellerGroupMap.containsKey(groupId)){
                List<JSONObject> list = this.shellerGroupMap.get(groupId);
                list.add(jsonObject);
                this.shellerGroupMap.put(groupId,list);
            }else{
                List<JSONObject> list = new ArrayList<>();
                list.add(jsonObject);
                this.shellerGroupMap.put(groupId,list);
            }
        }else if("del".equals(type)){//删除组
            if(this.shellerGroupMap.containsKey(groupId)){
                this.shellerGroupMap.remove(groupId);
            }
        }
    }

    public static void main(String[] args) throws Exception {
        String cmd = "bash /Users/wangjian/Desktop/test/test.sh";
        String params = "'1|2| 3 |4|' '222' q r";
        String shellId = "1111111";
        String logFilePath = "";
        Integer runTime = 10;

        Constants.SHELLER_CONTAINER.createSheller(cmd, params, shellId, logFilePath, runTime,"192.168.56.131","22", "root","1",false, new Sheller.Listener() {
            @Override
            public void postPid(String shellId, String pid) {

            }

            @Override
            public void onClose(String shellId,String desc,String status,String groupId) {
                System.out.println("+++++++++++++++++++++++++++");
                Constants.SHELLER_CONTAINER.cleanSheller(shellId);
            }
        });
//        Thread.sleep(10000);
//        Constants.SHELLER_CONTAINER.stopSheller(shellId);
        while (true){
            Thread.sleep(1000);
            logger.info(""+((ThreadPoolExecutor)Constants.SHELLER_CONTAINER.shellerThreadPool).getActiveCount());
        }
    }
}
